#pragma once

#include "NetTypes.h"
#include <vector>

namespace phynet{
namespace net{

class BitStream
{
public:
	static const Int32 STACK_BYTE_SIZE_FRAGMENT = 1024;
	static const Int32 DEFAULT_BYTE_SIZE_FRAGMENT = 1024;
	static const Int32 MAX_BYTE_SIZE_FRAGMENT = 1024;
	static const Int32 MAX_BYTE_SIZE_MESSAGE = 1048576;

public:
	//default ctor: stack BitStream
	BitStream();

	//heap ctor: Allocates (and owns) i_size byte.
	explicit BitStream(UInt32 i_size);

	//take an external buffer, no allocation needed and no deallocation will be performed
	//you can use an external buffer only to READ a BitStream
	BitStream(const Byte* i_externalBuffer, UInt32 i_bufferSize);

	//default dtor works well, so we don't need to define it
	//~BitStream();

	void Copy(const BitStream& i_stream);

	void Resize(const UInt32 i_size);
	//Copy Constructor and Copy Assignment should be allowed (but compiler generated is not good)
	//for now we disable copy between BitStreams
private:
	BitStream(const BitStream& i_other);
	BitStream&	operator=(const BitStream& i_other);

public:
	/**
		Reposition and Modifiers Methods
	*/
	void	Begin() const;
	void	End()	const;
	bool	SetBitPosition(UInt32 i_bitPosition)	const;
	bool	SetBytePosition(UInt32 i_bytePosition)	const;
	void	AlignByte();
	void	AlignByte() const;
	void	AlignNByte(UInt32 i_numBytes);
	void	AlignNByte(UInt32 i_numBytes) const;
	bool	IsAlignedByte()	const;
	void	Clear();

	/**
		Getters methods
	*/
	bool	IsEnd()	const;
	bool	IsBegin()	const;
	bool	Empty()	const;
	UInt32	GetBufferSize()	const;
	UInt32	GetByteSize()	const;
	UInt32	GetBitSize()	const;
	UInt32	GetBitPosition()	const;
	UInt32	GetCurrentByte()	const;
	UInt8	GetCurrentByteOffset()	const;
	const Byte*	GetCurrentPointer()	const;
	const Byte*	GetReadBuffer()	const;
	UInt32	GetRemainingByteSize()	const;

	/**
		Read and Write Value-type structs (i.e. those for which default copy works).
		Byte-Align memory and write sizeof(T) byte in the BitStream
		Application should use this when no in performance-critical session!
		Application should also avoid MESSY Template istantiation... use it carefully.
	*/
	template<class T> void	WriteType(const T& value);
	template<class T> void	ReadType(T& value)	const;

	/**
		Read&Write to/from BitStream.
		read/write nbits bits (if nbits<=sizeof(T))
		Useful for reading-writing Integral Type using less bit than necessary
		for example: 
			UInt32  x;
			UInt16  y;
			bool    z;
			WriteT(x, 12); // right usage ;)
			WriteT(y, 18); // wrong bitsize. clamped to sizeof(UInt16), i.e. 16
			WriteT(z,1);   // prefer WriteBit() instead.
	*/
	//WARNING: use it ONLY for integral values
	//Note: originally nbits was 8*sizeof(T)
	//however compiler seem to be not able to resolve this correctly and set
	//nbits to the first instance of WriteT.
	//To avoid this , I set nbits to 0 and I do as default behavior:
	//if(nbits == 0) nbits = sizeof(T)*8
	template<class T> void	WriteT(const T& value, UInt32 nbits = 0);
	template<class T> void	ReadT(T& value, UInt32 nbits = 0)	const;

	// DELTA WRITE
	/*
		this function has 3 params:
		- first one is the value to be written
		- second one is reference value... last value written
		- third one is the number of bits used for the delta write (default is sizeof(T))
	*/
	template<class T> void WriteIntDelta(T curr_val, T last_val, UInt32 nbits = 0);

	// DELTA READ
	/*
	this function has 3 params:
		- first one is reference value... last value written
		- second one is the number of bits used for the delta write (default is sizeof(T))
		- return value is the value read
	*/
	template<class T> T ReadIntDelta(T last_val, UInt32 nbits = 0) const;

	// WRITE ON STREAM
	void WriteUInt8(UInt8 val);
	void WriteUInt16(UInt16 val);
	void WriteUInt32(UInt32 val);
	void WriteUInt64(UInt64 val);
	void WriteInt8(Int8 val)		{WriteUInt8(static_cast<UInt8>(val));}
	void WriteInt16(Int16 val)		{WriteUInt16(static_cast<UInt16>(val));}
	void WriteInt32(Int32 val)		{WriteUInt32(static_cast<UInt32>(val));}
	void WriteInt64(Int64 val)		{WriteUInt64(static_cast<UInt64>(val));}
	void WriteReal(Real val);
	void WriteBit(Bit the_bit);
	void WriteBytes(const UInt8* data, Int32 size);	// aligns the cursor and writes size bytes
	void WriteBits(const UInt8* data, UInt32 bitSize);
	
	void AppendBitStream(const BitStream& i_stream);

	// READ FROM STREAM
	UInt8  ReadUInt8()  const;
	UInt16 ReadUInt16() const;
	UInt32 ReadUInt32() const;
	UInt64 ReadUInt64() const;
	Int8   ReadInt8()  const		{return static_cast<Int8>(ReadUInt8());}
	Int16  ReadInt16() const		{return static_cast<Int16>(ReadUInt16());}
	Int32  ReadInt32() const		{return static_cast<Int32>(ReadUInt32());}
	Int64  ReadInt64() const		{return static_cast<Int64>(ReadUInt64());}
	Real	ReadReal() const;
	Bit		ReadBit() const;
	void	ReadBytes(UInt8* data, Int32 size) const;	// aligns the cursor reads size bytes
	void	ReadBits(UInt8* data, UInt32 bitSize) const;

private:
	void	UpdateBitSize();

	Byte			m_stackBuffer[STACK_BYTE_SIZE_FRAGMENT];

private:
	Byte*			m_writingBuffer;
	const Byte*		m_readingBuffer;
	UInt32			m_bufferSize;

private:
	typedef	 std::vector<Byte>	Bytes;
	const Byte*				m_externalBuffer;
	Bytes					m_internalBuffer;
	UInt32					m_bitSize;
	mutable UInt32			m_currentBit;
};

/************************************************************************/
/* INLINE                                                               */
/************************************************************************/

inline void BitStream::AlignByte()
{
	m_currentBit = (m_currentBit + 0x07L) & ~0x07;
	UpdateBitSize();
}

inline void BitStream::AlignByte()	const
{
	m_currentBit = (m_currentBit + 0x07L) & ~0x07;
}

inline void BitStream::AlignNByte(UInt32 i_numBytes)
{
	AlignByte();
	m_currentBit += i_numBytes<<3;
	UpdateBitSize();
}

inline void BitStream::AlignNByte(UInt32 i_numBytes) const
{
	AlignByte();
	m_currentBit += i_numBytes<<3;
}

inline bool BitStream::IsAlignedByte()	const
{
	return (m_bitSize & 0x07) == 0;
}

inline void BitStream::Begin() const	
{
	m_currentBit = 0;
}

inline void BitStream::End() const	
{
	m_currentBit = m_bitSize;
}	

inline bool BitStream::IsEnd() const
{
	return m_currentBit == m_bitSize;
}

inline bool BitStream::IsBegin() const
{
	return m_currentBit == 0;
}

inline bool BitStream::Empty() const
{
	return m_bitSize == 0;
}

inline bool BitStream::SetBitPosition(UInt32 i_bitPosition)	const	
{
	if(i_bitPosition > m_bitSize)
		return false;
	m_currentBit = i_bitPosition;
	return true;
}

inline bool BitStream::SetBytePosition(UInt32 i_bytePosition)	const
{
	return SetBitPosition(i_bytePosition<<3);
}

inline UInt32 BitStream::GetBufferSize()	const 
{
	return m_bufferSize;
}

inline UInt32 BitStream::GetByteSize()	const 
{
	return (m_bitSize + 0x7) >> 3; 
}

inline UInt32 BitStream::GetBitSize()	const 
{
	return m_bitSize;
}

inline UInt32 BitStream::GetBitPosition()	const
{
	return m_currentBit;
}

inline UInt32 BitStream::GetCurrentByte()	const
{
	return m_currentBit>>3;
}

inline UInt8 BitStream::GetCurrentByteOffset()	const
{
	return static_cast<UInt8>(m_currentBit & 0x07L);
}

inline const Byte*	BitStream::GetReadBuffer() const
{
	return m_readingBuffer;
}

inline const Byte*	BitStream::GetCurrentPointer() const
{
	return m_readingBuffer+GetCurrentByte();
}

inline UInt32	BitStream::GetRemainingByteSize() const
{
	return GetByteSize() - GetCurrentByte();
}

inline void BitStream::Clear()	
{
	Begin();
	m_bitSize = 0;
}

inline void BitStream::UpdateBitSize()
{
	m_bitSize = max(m_bitSize,m_currentBit);
}

inline void BitStream::AppendBitStream(const BitStream& i_stream)
{
	WriteBits(i_stream.GetReadBuffer(),i_stream.GetBitSize());
}

/************************************************************************/
/* TEMPLATE                                                             */
/************************************************************************/

template<class T> void BitStream::WriteIntDelta(T curr_val, T last_val, UInt32 nbits)
{
	if(curr_val==last_val)
	{
		WriteBit(false);
	}else{
		WriteBit(true);
		WriteT(curr_val,nbits);
	}
}

template<class T> T BitStream::ReadIntDelta(T last_val, UInt32 nbits) const
{
	if(!ReadBit())
	{
		return last_val;
	}else{
		T temp;
		ReadT(temp,nbits);
		return temp;
	}
}

template<class T> void BitStream::WriteType(const T& value)
{
	const UInt8*	data = reinterpret_cast<const UInt8*>(&value);
	UInt32 size = sizeof(T);
	WriteBytes(data,size);
}

template<class T> void BitStream::ReadType(T& value) const
{
	UInt8*	data = reinterpret_cast<UInt8*>(&value);
	UInt32 size = sizeof(T);
	ReadBytes(data,size);
}

template<class T> void BitStream::WriteT(const T& value, const UInt32 nbits)
{
	const UInt32 trueBitSize = 8*sizeof(T) ;

	if((nbits == 0) || (nbits>=trueBitSize))
	{
		WriteType<T>(value);
	}else{
		UInt32 size = nbits;
		T temporaryValue = value;
		UInt8 temporaryByte = (UInt8)temporaryValue;
		while(size >= 8)
		{
			WriteUInt8(temporaryByte);
			temporaryValue >>= 8;
			size -= 8;
			temporaryByte = (UInt8)temporaryValue;
		}
		temporaryByte <<= (8-size);
		WriteBits(&temporaryByte,size);
	}
}

template<class T> void BitStream::ReadT(T& value, const UInt32 nbits)	const
{
	const UInt32 trueBitSize = 8*sizeof(T) ;

	if((nbits == 0) || (nbits>=trueBitSize))
	{
		ReadType<T>(value);
	}else{
		value = 0;
		UInt32 size = nbits;
		T temporaryValue = 0;
		UInt32 shift = 0;
		while(size >= 8)
		{
			temporaryValue = (T)ReadUInt8();
			temporaryValue <<= shift;
			value |= temporaryValue;
			shift += 8;
			size -= 8;
		}
		UInt8 temporaryByte;
		ReadBits(&temporaryByte,size);
		temporaryByte >>= (8-size);
		temporaryValue = (T)temporaryByte;
		temporaryValue <<= shift;
		value |= temporaryValue;
		value <<= (trueBitSize - nbits);
		value >>= (trueBitSize - nbits);
	}
}

template<class T> inline T BitReverse(T i_data)
{
	T ret = i_data; // ret will be reversed bits of i_data; first get LSB of i_data
	size_t shift = sizeof(i_data) * CHAR_BIT - 1; // extra shift needed at end

	for (i_data >>= 1; i_data; i_data >>= 1)
	{   
		ret <<= 1;
		ret |= i_data & 1;
		--shift;
	}
	ret <<= shift; // shift when i_data's highest bits are zero
	return ret;
}

}} //namespaces